package ru.spbu.math.android.cbrf.dataprovider;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import android.content.Context;
import android.util.Log;

import ru.spbu.math.android.cbrf.dataprovider.cbrf.CbrfProvider;
import ru.spbu.math.android.cbrf.dataprovider.randomprovider.RandomProvider;
import ru.spbu.math.android.cbrf.dataprovider.yandex.YandexProvider;
import ru.spbu.math.android.cbrf.model.Category;
import ru.spbu.math.android.cbrf.model.Rate;
import ru.spbu.math.android.cbrf.model.RateType;

/**
 * Class for providers managing.
 * This class implements IProvider interface
 */
public class ProviderManager {

	private final String LOG_TAG = "ProviderManager";
	
	private static ProviderManager instance;
	private DatabaseProvider databaseProvider;
	
	private List<IProvider> providerList;
	private Map<RateType, IProvider> rateTypeToIProvider;
	private Map<Category, List<RateType>> categoryToRateTypeList;
	
	public static ProviderManager getInstance(Context context) {
		if(instance == null){
			instance = new ProviderManager(context);
		}
		return instance;
	}
	
	private ProviderManager(Context context){
		providerList = new ArrayList<IProvider>();
		rateTypeToIProvider = new HashMap<RateType, IProvider>();
		categoryToRateTypeList = new HashMap<Category, List<RateType>>();
		
		databaseProvider = new DatabaseProvider(context, this);
		
		IProvider cbrfProvider = new CbrfProvider(context.getResources());
		IProvider yandexProvider = new YandexProvider(context.getResources());
		IProvider randomProvider = new RandomProvider();
		Log.d(LOG_TAG, "Start registration of CbrfProvider");
		addProvider(cbrfProvider);
		Log.d(LOG_TAG, "Start registration of YandexProvider");
		addProvider(yandexProvider);
		Log.d(LOG_TAG, "Start registration of RandomProvider");
		addProvider(randomProvider);
		Log.d(LOG_TAG, "End registration");
	}
	
	/**
	 * Registering new provider
	 * @param provider
	 */
	public void addProvider(IProvider provider){
		Log.d(LOG_TAG, "Registration IProvider from class: " + provider.getClass().getName());
		
		for(int i = 0; i < providerList.size(); i++){
			if(providerList.get(i).getClass().getName().equals(provider.getClass().getName())){
				return;
			}
		}
		
		Collection<Category> categoryCollection =  provider.getCategories();
		Iterator<Category> categoryIterator = categoryCollection.iterator();
		
		while(categoryIterator.hasNext()){
			Category category = categoryIterator.next();
			Log.d(LOG_TAG, "Category: " + category.getName());
			List<RateType> rateTypeList = new ArrayList<RateType>();	
			rateTypeList = (List<RateType>) provider.getRateTypeByCategory(category);
			for(int j = 0; j < rateTypeList.size(); j++){
				rateTypeToIProvider.put(rateTypeList.get(j), provider);
			}	
			if(!categoryToRateTypeList.containsKey(category)){
				Log.d(LOG_TAG, "New category");
				Collections.sort(rateTypeList, RateType.getComparator());
				categoryToRateTypeList.put(category, rateTypeList);
			}
			else{
				Log.d(LOG_TAG, "Category is already exist");
				List<RateType> existRateTypeList = categoryToRateTypeList.remove(category);
				existRateTypeList.addAll(rateTypeList);
				Collections.sort(existRateTypeList, RateType.getComparator());
				categoryToRateTypeList.put(category, existRateTypeList);	
			}
		}
		providerList.add(provider);
	}
	
	/**
	 * Get external id by RateType
	 * @param rateType
	 * @return id
	 */
	public String getExternalIdByRateType(RateType rateType){
		if(rateTypeToIProvider.containsKey(rateType)){
			IProvider provider = rateTypeToIProvider.get(rateType);
			String externalId= provider.getClass().getName() + ":" + rateType.getId();
			return externalId;
		}
		return null;
	}
	
	/**
	 * Get RateType by external id
	 * @param externalId
	 * @return RateType
	 */
	public RateType getRateTypeByExternalId(String externalId){
		String[] dividedId = externalId.split(":");
		IProvider provider = getIProviderByExternalId(externalId);
		
		Collection<Category> categoryCollection =  provider.getCategories();
		Iterator<Category> categoryIterator = categoryCollection.iterator();
		
		while(categoryIterator.hasNext()){
			Category category = categoryIterator.next();
			List<RateType> rateTypeList = new ArrayList<RateType>();	
			rateTypeList = (List<RateType>) provider.getRateTypeByCategory(category);
			for(int j = 0; j < rateTypeList.size(); j++){
				if(rateTypeList.get(j).getId().equals(dividedId[1])){
					return rateTypeList.get(j);
				}
			}
		}
		return null;
	}
	
	/**
	 * Get RateType internal id by external id
	 * @param externalId
	 * @return internal id
	 */
	public String getInternalId(String externalId) {
		String[] dividedId = externalId.split(":");
		return dividedId[1];
	}

	/**
	 * Get provider by external id
	 * @param externalId
	 * @return provider
	 */
	public IProvider getIProviderByExternalId(String externalId){
		String[] dividedId = externalId.split(":");
		
		Collection<IProvider> providerList = (Collection<IProvider>) rateTypeToIProvider.values();
		
		Iterator<IProvider> iterator = providerList.iterator();
		while (iterator.hasNext()) {
			IProvider provider = iterator.next();
			if (provider.getClass().getName().equals(dividedId[0])) {
				return provider;
			}
		}
		return null;
	}


	public List<Category> getCategories() {
		// it's very strange, but it is the simplest way to make sorted Collection from Set
		Category[] arr = new Category[categoryToRateTypeList.keySet().size()];
		List<Category> categories = Arrays.asList(categoryToRateTypeList.keySet().toArray(arr));
		Collections.sort(categories, Category.getComparator());
		return categories;
	}

	public Rate getCurrentRate(String id){
		Log.d(LOG_TAG, "Get Current Rate");
		IProvider provider = getIProviderByExternalId(id);
		Log.d(LOG_TAG, "Provider: " + provider.getName());
		try {
			Rate rate = provider.getCurrentRate(getInternalId(id));
			databaseProvider.addLatestRate(rate);
			return rate;
		} catch (UnavailableValueException e) {
			Log.e(LOG_TAG, "UnavailableValueException");
			return databaseProvider.getLatestRate(getRateTypeByExternalId(id));
		}
	}

	public List<Rate> getDynamicRates(String id, Date since, Date until){
		IProvider provider = getIProviderByExternalId(id);
		try {
			List<Rate> rateList = provider.getDynamicRates(getInternalId(id), since, until);
			return rateList;
		} catch (UnavailableValueException e) {
			Log.e(LOG_TAG, "UnavailableValueException");
			return null;
		}
	}

	public List<RateType> getRateTypeByCategory(Category category) {
		return categoryToRateTypeList.get(category);
	}
	
	public String getProviderName(RateType rateType) {
		return rateTypeToIProvider.get(rateType).getName();
	}
}
